/**
 * DragonBonesArmature Class
 * 
 * A simplified implementation of the DragonBones armature for managing
 * bones, slots, and animations.
 */

import { 
  ArmatureData, 
  BoneData, 
  SlotData, 
  AnimationData,
  Bone,
  Slot,
  Display,
  Animation,
  Transform
} from './types';
import { DragonBonesFactory } from './DragonBonesFactory';
import { DragonBonesAnimation } from './DragonBonesAnimation';
import { Logger } from '../../../utils/Logger';

export class DragonBonesArmature {
  public name: string;
  public bones: Map<string, Bone> = new Map();
  public slots: Slot[] = [];
  public animations: Map<string, Animation> = new Map();
  public currentAnimation: Animation | null = null;
  private factory: DragonBonesFactory;
  private boneMap: Map<string, BoneData> = new Map();
  private slotMap: Map<string, SlotData> = new Map();

  constructor(armatureData: ArmatureData, factory: DragonBonesFactory) {
    this.name = armatureData.name;
    this.factory = factory;
    
    const logger = Logger.getInstance();
    logger.log(`Creating armature: ${this.name}`, 'DragonBonesArmature');
    
    // Store data for reference
    for (const boneData of armatureData.bone) {
      this.boneMap.set(boneData.name, boneData);
    }
    
    for (const slotData of armatureData.slot) {
      this.slotMap.set(slotData.name, slotData);
    }
    
    // Create bones
    this.createBones(armatureData.bone);
    
    // Create slots
    this.createSlots(armatureData.slot);
    
    // Create animations
    if (armatureData.animation) {
      this.createAnimations(armatureData.animation);
    }
    
    logger.log(`Successfully created armature: ${this.name} with ${this.bones.size} bones, ${this.slots.length} slots, and ${this.animations.size} animations`, 'DragonBonesArmature');
  }

  /**
   * Creates bones from bone data
   * @param boneDataArray - Array of bone data
   */
  private createBones(boneDataArray: BoneData[]): void {
    const logger = Logger.getInstance();
    logger.log(`Creating ${boneDataArray.length} bones`, 'DragonBonesArmature');
    
    // Create a map to store bone creation order (parents first)
    const boneCreationQueue: BoneData[] = [];
    const createdBones = new Set<string>();
    
    // Add all bones to the queue
    boneCreationQueue.push(...boneDataArray);
    
    // Process bones in the correct order (parents before children)
    while (boneCreationQueue.length > 0) {
      const boneData = boneCreationQueue.shift();
      if (!boneData) break;
      
      // Check if parent bone exists (if specified)
      if (boneData.parent && !createdBones.has(boneData.parent)) {
        // Parent not created yet, put this bone back at the end of the queue
        boneCreationQueue.push(boneData);
        continue;
      }
      
      // Create the bone
      const bone: Bone = {
        name: boneData.name,
        parent: boneData.parent ? this.bones.get(boneData.parent) || null : null,
        transform: this.parseTransform(boneData.transform),
        globalTransform: { x: 0, y: 0, rotation: 0, scaleX: 1, scaleY: 1 }
      };
      
      this.bones.set(bone.name, bone);
      createdBones.add(bone.name);
      logger.log(`Created bone: ${bone.name}`, 'DragonBonesArmature');
    }
  }

  /**
   * Creates slots from slot data
   * @param slotDataArray - Array of slot data
   */
  private createSlots(slotDataArray: SlotData[]): void {
    const logger = Logger.getInstance();
    logger.log(`Creating ${slotDataArray.length} slots`, 'DragonBonesArmature');
    
    for (const slotData of slotDataArray) {
      const parentBone = this.bones.get(slotData.parent);
      if (!parentBone) {
        logger.warn(`Parent bone ${slotData.parent} not found for slot ${slotData.name}`, 'DragonBonesArmature');
        continue;
      }
      
      // Create displays for this slot
      const displays: Display[] = [];
      if (slotData.display) {
        for (const displayData of slotData.display) {
          const display: Display = {
            name: displayData.name,
            type: displayData.type,
            path: displayData.path
          };
          
          // If this is an image display, try to load the texture
          if (display.type === 'image' && display.path) {
            const textureAtlas = this.factory.getTextureAtlasData();
            if (textureAtlas) {
              // Find the texture in the atlas
              const subTexture = textureAtlas.SubTexture.find(st => st.name === display.path);
              if (subTexture) {
                // Load the texture
                const texture = this.factory.getTexture(textureAtlas.imagePath);
                if (texture) {
                  display.texture = texture;
                }
              }
            }
          }
          
          displays.push(display);
        }
      }
      
      const slot: Slot = {
        name: slotData.name,
        parent: parentBone,
        display: displays[0] || { name: 'default', type: 'none' },
        displays: displays
      };
      
      this.slots.push(slot);
      logger.log(`Created slot: ${slot.name}`, 'DragonBonesArmature');
    }
  }

  /**
   * Creates animations from animation data
   * @param animationDataArray - Array of animation data
   */
  private createAnimations(animationDataArray: AnimationData[]): void {
    const logger = Logger.getInstance();
    logger.log(`Creating ${animationDataArray.length} animations`, 'DragonBonesArmature');
    
    for (const animationData of animationDataArray) {
      const animation = new DragonBonesAnimation(animationData, this);
      this.animations.set(animationData.name, animation);
      logger.log(`Created animation: ${animationData.name}`, 'DragonBonesArmature');
    }
  }

  /**
   * Parses transform data into a Transform object
   * @param transformData - The transform data to parse
   * @returns A Transform object
   */
  private parseTransform(transformData: any = {}): Transform {
    return {
      x: transformData.x || 0,
      y: transformData.y || 0,
      rotation: transformData.skX || transformData.skY || 0,
      scaleX: transformData.scX || 1,
      scaleY: transformData.scY || 1
    };
  }

  /**
   * Plays an animation
   * @param animationName - The name of the animation to play
   * @param playTimes - Number of times to play (0 = loop)
   */
  public play(animationName: string, playTimes: number = 0): void {
    const logger = Logger.getInstance();
    const animation = this.animations.get(animationName);
    
    if (!animation) {
      logger.warn(`Animation not found: ${animationName}`, 'DragonBonesArmature');
      return;
    }
    
    animation.playTimes = playTimes;
    this.currentAnimation = animation;
    animation.reset();
    logger.log(`Playing animation: ${animationName}`, 'DragonBonesArmature');
  }

  /**
   * Advances the animation time
   * @param deltaTime - Time elapsed since last frame
   */
  public advanceTime(deltaTime: number): void {
    if (this.currentAnimation) {
      this.currentAnimation.advanceTime(deltaTime);
    }
  }

  /**
   * Updates the global transforms of all bones
   */
  public updateTransforms(): void {
    // Update bone global transforms
    for (const bone of this.bones.values()) {
      if (bone.parent) {
        // Calculate global transform based on parent
        bone.globalTransform.x = bone.parent.globalTransform.x + bone.transform.x;
        bone.globalTransform.y = bone.parent.globalTransform.y + bone.transform.y;
        bone.globalTransform.rotation = bone.parent.globalTransform.rotation + bone.transform.rotation;
        bone.globalTransform.scaleX = bone.parent.globalTransform.scaleX * bone.transform.scaleX;
        bone.globalTransform.scaleY = bone.parent.globalTransform.scaleY * bone.transform.scaleY;
      } else {
        // Root bone
        bone.globalTransform = { ...bone.transform };
      }
    }
  }
}